﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using SL.M.Text;

namespace SL.M.ManagedTest
{

    /// <summary>
    /// Core test methods.
    /// </summary>
    [TestClass]
    [DeploymentItem(@"7zip\7za.exe")]
    [DeploymentItem(@"7zip\7zS.sfx")]
    [DeploymentItem(@"7zip\7zS2.sfx")]
    [DeploymentItem(@"7zip\7zS2con.sfx")]
    [DeploymentItem(@"7zip\7zSD.sfx")]
    public class CoreTest
    {
        class ArchiverTestChild1 : GameObject
        {
            public ArchiverTestChild1()
            {
                b = new byte[4];
                for (int i = 0; i < _A3.Length; ++i)
                {
                    _A3[i] = null;
                }
                b[0] = 1;
                b[1] = 2;
                b[2] = 3;
                b[3] = 4;
            }

            protected override void Read(Archiver archiver)
            {
                int sz = 0;
                if (archiver.ReadArrayStart("A3", ref sz))
                {
                    Assert.AreEqual(sz, _A3.Length);
                    for (int i = 0; i < sz; ++i)
                    {
                        _A3[i] = archiver.ReadArrayString(i);
                    }
                    archiver.ReadArrayEnd();
                }

                b = archiver.ReadBytes("b");
                Assert.AreEqual(4, b.Length);
                Assert.AreEqual((byte)1, b[0]);
                Assert.AreEqual((byte)2, b[1]);
                Assert.AreEqual((byte)3, b[2]);
                Assert.AreEqual((byte)4, b[3]);
            }

            protected override void Write(Archiver archiver)
            {
                archiver.WriteArrayStart("A3");
                for (int i = 0; i < _A3.Length; ++i)
                {
                    archiver.WriteArrayValue(_A3[i]);
                }
                archiver.WriteArrayEnd();

                archiver.WriteBytes("b", b);
                Assert.AreEqual((byte)1, b[0]);
                Assert.AreEqual((byte)2, b[1]);
                Assert.AreEqual((byte)3, b[2]);
                Assert.AreEqual((byte)4, b[3]);
            }

            public String[] _A3 = new String[2];
            public byte[] b;
        }

        class ArchiverTestChild2 : GameObject
        {
            public ArchiverTestChild2()
            {
                _Ch1 = null;
                for (int i = 0; i < _A1.Length; ++i)
                {
                    _A1[i] = 0;
                }
                for (int i = 0; i < _A2.Length; ++i)
                {
                    _A2[i] = false;
                }
            }

            protected override void Read(Archiver archiver)
            {
                int sz = 0;
                if (archiver.ReadArrayStart("_A1", ref sz))
                {
                    Assert.AreEqual(sz, _A1.Length + 1);
                    for (int i = 0; i < 3; ++i)
                    {
                        _A1[i] = archiver.ReadArrayDouble(i);
                    }

                    int sz2 = 0;
                    if (archiver.ReadArrayStart(3, ref sz2))
                    {
                        Assert.AreEqual(sz2, _A2.Length);
                        for (int i = 0; i < sz2; ++i)
                        {
                            _A2[i] = archiver.ReadArrayBool(i);
                        }
                        archiver.ReadArrayEnd();
                    }
                    archiver.ReadArrayEnd();
                }

                archiver.ReadObject("_Ch1", ref _Ch1);
            }

            protected override void Write(Archiver archiver)
            {
                archiver.WriteValue("_Ch1", _Ch1);

                archiver.WriteArrayStart("_A1");
                for (int i = 0; i < _A1.Length; ++i)
                {
                    archiver.WriteArrayValue(_A1[i]);
                }

                archiver.WriteArrayStart();
                for (int i = 0; i < _A2.Length; ++i)
                {
                    archiver.WriteArrayValue(_A2[i]);
                }
                archiver.WriteArrayEnd();

                archiver.WriteArrayEnd();
            }

            public double[] _A1 = new double[3];
            public bool[] _A2 = new bool[3];
            public ArchiverTestChild1 _Ch1;
        }

        class ArchiverTestRoot : GameObject
        {
            public ArchiverTestRoot()
            {

            }

            protected override void Read(Archiver archiver)
            {
                a = archiver.ReadBool("a");
                v1 = archiver.ReadInt("v1");
                v2 = archiver.ReadLong("v2");
                v3 = archiver.ReadDouble("v3");
                v4 = archiver.ReadSByte("v4");
                v5 = archiver.ReadShort("v5");
                str = archiver.ReadString("str");
                archiver.ReadObject("_Ch1", ref _Ch1);
                archiver.ReadObject("_Ch2", ref _Ch2);

                bool aa;
                Int32 av1;
                Int64 av2;
                double av3;
                SByte av4;
                Int16 av5;
                String astr;
                ArchiverTestChild1 a_Ch1 = null;
                ArchiverTestChild2 a_Ch2 = null;

                int sz = 0;
                Assert.IsTrue(archiver.ReadArrayStart("arr", ref sz));
                Assert.AreEqual(sz, 9);
                aa = archiver.ReadArrayBool(0);
                Assert.AreEqual(a, aa);
                av1 = archiver.ReadArrayInt(1);
                Assert.AreEqual(v1, av1);
                av2 = archiver.ReadArrayLong(2);
                Assert.AreEqual(v2, av2);
                av3 = archiver.ReadArrayDouble(3);
                Assert.AreEqual(v3, av3);
                av4 = archiver.ReadArraySByte(4);
                Assert.AreEqual(v4, av4);
                av5 = archiver.ReadArrayShort(5);
                Assert.AreEqual(v5, av5);
                astr = archiver.ReadArrayString(6);
                Assert.AreEqual(str, astr);
                archiver.ReadArrayObject(7, ref a_Ch1);
                Assert.AreEqual(_Ch1, a_Ch1);
                archiver.ReadArrayObject(8, ref a_Ch2);
                Assert.AreEqual(_Ch2, a_Ch2);
                archiver.ReadArrayEnd();
            }

            protected override void Write(Archiver archiver)
            {
                archiver.WriteValue("_Ch1", _Ch1);
                archiver.WriteValue("_Ch2", _Ch2);
                archiver.WriteValue("a", a);
                archiver.WriteValue("v1", v1);
                archiver.WriteValue("v2", v2);
                archiver.WriteValue("v3", v3);
                archiver.WriteValue("v4", v4);
                archiver.WriteValue("v5", v5);
                archiver.WriteValue("str", str);

                archiver.WriteArrayStart("arr");
                archiver.WriteArrayValue(a);
                archiver.WriteArrayValue(v1);
                archiver.WriteArrayValue(v2);
                archiver.WriteArrayValue(v3);
                archiver.WriteArrayValue(v4);
                archiver.WriteArrayValue(v5);
                archiver.WriteArrayValue(str);
                archiver.WriteArrayValue(_Ch1);
                archiver.WriteArrayValue(_Ch2);
                archiver.WriteArrayEnd();
            }

            public bool a;
            public Int32 v1;
            public Int64 v2;
            public double v3;
            public SByte v4;
            public Int16 v5;
            public String str;
            public ArchiverTestChild1 _Ch1;
            public ArchiverTestChild2 _Ch2;
        }


        class InternalProcess : Process
        {
            public InternalProcess()
            {
                _Hnd = new ManualResetEventSlim();
                _Messages = new List<Message>();
            }

            public void Reset()
            {
                if (_Hnd != null)
                {
                    _Hnd.Reset();
                }
            }

            public Message WaitMessage(string messageType)
            {
                _Hnd.Wait(30000);
                Message ret = FindMessage(messageType);
                if (ret != null)
                {
                    return ret;
                }
                return null;
            }

            Message FindMessage(string messageType)
            {
                lock (_MessageLock)
                {
                    foreach (Message m in _Messages)
                    {
                        if (m.TypeEquals(messageType))
                        {
                            _Messages.Clear();
                            return m;
                        }
                    }
                }
                return null;
            }

            object _MessageLock = new object();
            List<Message> _Messages;
            ManualResetEventSlim _Hnd;

            protected override void ProcessEntry()
            {
                // Now run the main message loop.
                while (Started)
                {
                    NextMessage();
                }
            }

            protected override bool HandleMessage(Message message)
            {
                lock (_MessageLock)
                {
                    _Messages.Add(message);
                }
                _Hnd.Set();
                return true;
            }
        }

        class ProcessObject : GameObject
        {
            public string s;

            protected override void Read(Archiver archiver)
            {
                base.Read(archiver);

                s = archiver.ReadString("s");
            }

            protected override void Write(Archiver archiver)
            {
                base.Write(archiver);

                archiver.WriteValue("s", s);
            }
        }

        class InternalStateProcess : Process
        {
            ManualResetEventSlim _Hnd = new ManualResetEventSlim();

            public string TimerId
            {
                get; set;
            }

            // Perform some self-testing of various process static methods.

            public void Reset()
            {
                if (_Hnd != null)
                {
                    _Hnd.Reset();
                }
            }
            
            public InternalStateProcess()
            {

            }

            public void WaitHandle()
            {
                _Hnd.Wait();
            }

            protected override void ProcessEntry()
            {
                Message m;

                for (;;)
                {
                    m = Process.WaitAnyFromProcess(null, "us.sl.m.start");
                    if (m.TypeEquals("us.sl.m.start") && m.Data == this)
                    {
                        break;
                    }
                }

                m = Process.Wait();
                Assert.AreEqual(m.TypeId, "us.sl.mcore.frame");

                m = Process.WaitAnyFromProcess(this, "us.sl.managedtest.0", "us.sl.managedtest.1");
                Assert.AreEqual(m.TypeId, "us.sl.managedtest.0");
                Assert.AreEqual(m.Sender, this);

                ICollection<Message> collected = Process.WaitAllFromProcess(this, "us.sl.managedtest.2", "us.sl.managedtest.3");
                int type = 2;
                foreach (Message msg in collected)
                {
                    Assert.AreEqual(m.Sender, this);
                    Assert.AreEqual(msg.TypeId, "us.sl.managedtest." + type);
                    ++type;
                }

                collected = Process.WaitAll("us.sl.managedtest.0", "us.sl.managedtest.1", "us.sl.managedtest.2", "us.sl.managedtest.3");
                type = 0;
                foreach (Message msg in collected)
                {
                    Assert.AreEqual(msg.TypeId, "us.sl.managedtest." + type);
                    ++type;
                }

                Process.WriteText("bar.txt", "1234", TextEncoding.Utf16);
                string text = Process.ReadText("bar.txt");
                Assert.AreEqual("1234", text);

                using (TimerState ts = Process.Current.SetOneShotTimer(TimeSpan.FromSeconds(1), TimerId, this))
                {
                    m = Process.WaitAny(TimerId);
                    Assert.AreEqual(this, m.Sender);
                    Assert.AreEqual(TimerId, m.TypeId);
                    Assert.AreEqual(this, m.Data);
                }

                using (TimerState ts = Process.Current.SetOneShotTimer(TimeSpan.FromSeconds(1), TimerId))
                {
                    m = Process.WaitAny(TimerId);
                    Assert.AreEqual(this, m.Sender);
                    Assert.AreEqual(TimerId, m.TypeId);
                    Assert.AreEqual(null, m.Data);
                }

                using (TimerState ts = Process.Current.SetPeriodicTimer(TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100), TimerId, this))
                {
                    m = Process.WaitAny(TimerId);
                    Assert.AreEqual(this, m.Sender);
                    Assert.AreEqual(TimerId, m.TypeId);
                    Assert.AreEqual(this, m.Data);
                    m = Process.WaitAny(TimerId);
                    Assert.AreEqual(this, m.Sender);
                    Assert.AreEqual(TimerId, m.TypeId);
                    Assert.AreEqual(this, m.Data);
                }

                // TODO: add the last overload. 

                Process.WriteFile("foo.txt", new byte[] { 1, 2, 3, 4, 5 });
                byte[] read = Process.ReadFile("foo.txt");
                Assert.AreEqual(read[0], 1);
                Assert.AreEqual(read[1], 2);
                Assert.AreEqual(read[2], 3);
                Assert.AreEqual(read[3], 4);
                Assert.AreEqual(read[4], 5);

                Package pkg = new Package();
                pkg.FilesByData.Add(new KeyValuePair<string, byte[]>("foo.txt", new byte[] { 1, 2, 3, 4, 5 }));
                pkg.FilesByPath.Add(new KeyValuePair<string, string>("foo2.txt", "foo.txt"));
                ProcessObject o = new ProcessObject();
                o.s = "bar";
                pkg.Metadata = o;

                Process.WriteFile("pkg.pkg", pkg);
                Assert.IsTrue(File.Exists("pkg.pkg"));
                string metadata = Process.ReadText(Path.Combine("pkg.pkg", Package.MetadataFileName));
                TextArchiver a = new TextArchiver();
                o.s = null;
                a.Unarchive(o, metadata);
                Assert.IsTrue(String.Compare("bar", o.s) == 0);

                _Hnd.Set();
                Process.WaitAnyFromProcess(null, "us.sl.m.stop");
                _Hnd.Set();
            }
        }

        [TestMethod]
        public void ManagedTestGameObject()
        {
            using (GameObject obj = new GameObject())
            {

            }
        }

        [TestMethod]
        public void ManagedTestTextArchiver()
        {
            TextArchiver a = new TextArchiver();
            GameObject o = CreateObjectGraph();
            GameObject o2 = CreateObjectGraph();

            InitGraph(o);
            string s = a.Archive(o);
            Trace.WriteLine(s);
            TestArchive(a, o);
            a.Unarchive(o2, s);
            TestArchive(a, o2);
        }

        [TestMethod]
        public void ManagedTestBinaryArchiver()
        {
            BinaryArchiver a = new BinaryArchiver();
            GameObject o = CreateObjectGraph();
            GameObject o2 = CreateObjectGraph();

            InitGraph(o);
            byte[] b = a.Archive(o);
            TestArchive(a, o);
            a.Unarchive(o2, b);
            TestArchive(a, o2);
        }

        [TestMethod]
        public void ManagedTestGame()
        {
            const int NumProcesses = 4;

            using (Game g = new Game())
            {
                Assert.IsFalse(g.Started);
                g.Start();
                Assert.IsTrue(g.Started);
                InternalProcess[] p = new InternalProcess[NumProcesses];

                for (int i = 0; i < NumProcesses; ++i)
                {
                    p[i] = new InternalProcess();
                    g.AddProcess(p[i]);
                    Assert.IsTrue(p[i].Started);
                }
                g.Flush();

                for (int i = 0; i < NumProcesses; ++i)
                {
                    Message m = p[i].WaitMessage("us.sl.m.start");
                    Assert.IsNotNull(m);
                    Assert.AreEqual("us.sl.m.start", m.TypeId);
                    p[i].Reset();
                }

                // Assume the third message argument is covered. :)
                p[0].Send("us.sl.managedtest.0", null);
                g.Flush();

                Assert.IsTrue(g.Resumed);

                for (int i = 0; i < NumProcesses; ++i)
                {
                    Message m = p[i].WaitMessage("us.sl.managedtest.0");
                    Assert.IsNotNull(m);
                    Assert.AreEqual(p[0], m.Sender);
                    Assert.AreEqual("us.sl.managedtest.0", m.TypeId);
                    p[i].Reset();
                    Assert.IsTrue(p[i].Resumed);
                }

                g.Pause();
                Assert.IsFalse(g.Resumed);
                for (int i = 0; i < NumProcesses; ++i)
                {
                    Assert.IsFalse(p[i].Resumed);
                }

                p[0].Send("us.sl.managedtest.0", p[0], true);

                Assert.IsFalse(g.Resumed);
                g.Resume();
                Assert.IsTrue(g.Resumed);
                g.Flush();

                for (int i = 0; i < NumProcesses; ++i)
                {
                    Message m = p[i].WaitMessage("us.sl.managedtest.0");
                    Assert.IsNotNull(m);
                    Assert.AreEqual(p[0], m.Sender);
                    Assert.AreEqual("us.sl.managedtest.0", m.TypeId);
                    Assert.AreEqual(p[0], m.Data);
                    p[i].Reset();
                }

                p[0].Send("us.sl.managedtest.0", p[0], true);

                g.Flush();

                for (int i = 0; i < NumProcesses; ++i)
                {
                    Message m = p[i].WaitMessage("us.sl.managedtest.0");
                    Assert.IsNotNull(m);
                    Assert.AreEqual(p[0], m.Sender);
                    Assert.AreEqual("us.sl.managedtest.0", m.TypeId);
                    Assert.AreEqual(p[0], m.Data);
                    p[i].Reset();
                }

                p[0].Send("us.sl.managedtest.0", p[0]);

                for (int i = 0; i < NumProcesses; ++i)
                {
                    if (i % 2 == 0)
                    {
                        g.RemoveProcess(p[i]);
                    }
                }

                g.Flush();

                for (int i = 0; i < NumProcesses; ++i)
                {
                    Message m = p[i].WaitMessage("us.sl.m.stop");
                    Assert.IsNotNull(m);
                    if (i % 2 == 0)
                    {
                        Assert.IsFalse(p[i].Started);
                    }
                    Assert.AreEqual(null, m.Sender);
                    Assert.AreEqual("us.sl.m.stop", m.TypeId);
                    Assert.IsFalse(null == m.Data);
                    p[i].Reset();
                }

                for (int i = 0; i < NumProcesses; ++i)
                {
                    if (i % 2 == 1)
                    {
                        p[i].Send("us.sl.managedtest.0", p[0]);
                    }
                }

                g.Flush();

                for (int i = 0; i < NumProcesses; ++i)
                {
                    if (i % 2 == 1)
                    {
                        Message m = p[i].WaitMessage("us.sl.managedtest.0");
                        Assert.IsNotNull(m);
                        Assert.AreEqual("us.sl.managedtest.0", m.TypeId);
                        p[i].Reset();
                    }
                }

                Assert.IsTrue(g.Started);
                g.Stop();
                for (int i = 0; i < NumProcesses; ++i)
                {
                    if (i % 2 == 1)
                    {
                        Message m = p[i].WaitMessage("us.sl.m.stop");
                        Assert.IsFalse(p[i].Started);
                        Assert.IsFalse(p[i].Resumed);
                        Assert.AreEqual("us.sl.m.stop", m.TypeId);
                    }
                }
                Assert.IsFalse(g.Started);
            }
        }

        [TestMethod]
        public void ManagedTestProcess()
        {
            const int NumProcesses = 1;

            using (Game g = new Game())
            {
                Assert.IsFalse(g.Started);
                g.Start();
                Assert.IsTrue(g.Started);
                InternalStateProcess[] p = new InternalStateProcess[NumProcesses];

                for (int i = 0; i < NumProcesses; ++i)
                {
                    p[i] = new InternalStateProcess();
                    p[i].TimerId = "us.sl.managedtest." + (100 + i);
                    g.AddProcess(p[i]);
                    Assert.IsTrue(p[i].Started);
                    p[i].Send("us.sl.mcore.frame");
                    p[i].Send("us.sl.managedtest.0");
                    p[i].Send("us.sl.managedtest.1");
                    p[i].Send("us.sl.managedtest.2");
                    p[i].Send("us.sl.managedtest.3");
                }
                g.Flush();

                p[0].Send("us.sl.managedtest.0");
                p[0].Send("us.sl.managedtest.1");
                p[0].Send("us.sl.managedtest.2");
                p[0].Send("us.sl.managedtest.3");

                for (int i = 0; i < NumProcesses; ++i)
                {
                    p[i].WaitHandle();
                    p[i].Reset();
                }

                Assert.IsTrue(g.Started);
                g.Stop();

                for (int i = 0; i < NumProcesses; ++i)
                {
                    p[i].WaitHandle();
                    Assert.IsFalse(p[i].Started);
                }
                Assert.IsFalse(g.Started);
            }
        }

        void InitGraph(GameObject o)
        {
            ArchiverTestRoot root = (ArchiverTestRoot)o;
            Assert.IsNotNull(root);
            root.a = true;
            root.str = "foo";
            root.v1 = 1;
            root.v2 = 2;
            root.v3 = 3;
            root.v4 = 4;
            root.v5 = 5;

            root._Ch1 = new ArchiverTestChild1();
            root._Ch1._A3[0] = null;
            root._Ch1._A3[1] = "baz";

            root._Ch2 = new ArchiverTestChild2();
            root._Ch2._A1[0] = 1;
            root._Ch2._A1[1] = 2;
            root._Ch2._A1[2] = 3;
            root._Ch2._A2[0] = true;
            root._Ch2._A2[1] = false;
            root._Ch2._A2[2] = true;
            root._Ch2._Ch1 = root._Ch1;
        }

        void TestArchive(Archiver a, GameObject o)
        {
            ArchiverTestRoot root = (ArchiverTestRoot)o;
            Assert.IsNotNull(root);
            Assert.AreEqual(true, root.a);
            Assert.IsTrue(String.Compare(root.str, "foo") == 0);
            Assert.AreEqual(1, root.v1);
            Assert.AreEqual(2, root.v2);
            Assert.AreEqual(3, root.v3);
            Assert.AreEqual(4, root.v4);
            Assert.AreEqual(5, root.v5);

            Assert.IsNotNull(root._Ch1);
            Assert.IsNull(root._Ch1._A3[0]);
            Assert.IsTrue(String.Compare(root._Ch1._A3[1], "baz") == 0);

            Assert.IsNotNull(root._Ch2);
            Assert.AreEqual(1, root._Ch2._A1[0]);
            Assert.AreEqual(2, root._Ch2._A1[1]);
            Assert.AreEqual(3, root._Ch2._A1[2]);
            Assert.AreEqual(true, root._Ch2._A2[0]);
            Assert.AreEqual(false, root._Ch2._A2[1]);
            Assert.AreEqual(true, root._Ch2._A2[2]);
            Assert.AreEqual(root._Ch1, root._Ch2._Ch1);
        }

        GameObject CreateObjectGraph()
        {
            ArchiverTestRoot r = new ArchiverTestRoot();
            return r;
        }
    }
}
